from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from uuid import UUID
import redis.asyncio as redis
import json
from src.core.config import settings
from src.core.security import (
    hash_password,
    verify_password,
    create_access_token,
    create_refresh_token,
    generate_password_reset_token,
    check_password_strength
)
from src.models.schemas.auth import UserCreate, UserLogin, UserUpdate, PasswordResetRequest
from src.models.domain.user import User
from src.repositories.user import UserRepository


class AuthService:
    def __init__(self, user_repository: UserRepository, redis_client: redis.Redis):
        self.user_repository = user_repository
        self.redis_client = redis_client
    
    async def register_user(self, user_data: UserCreate) -> tuple[Optional[User], Optional[str]]:
        """Register a new user."""
        # Check if email already exists
        if await self.user_repository.exists_by_email(user_data.email):
            return None, "Email already registered"
        
        # Check if username already exists
        if await self.user_repository.exists_by_username(user_data.username):
            return None, "Username already taken"
        
        # Check password strength
        is_strong, message = check_password_strength(user_data.password)
        if not is_strong:
            return None, message
        
        # Hash password
        password_hash = hash_password(user_data.password)
        
        # Create user
        user = await self.user_repository.create(user_data, password_hash)
        if not user:
            return None, "Failed to create user"
        
        # Generate JWT token
        token = create_access_token({
            "user_id": str(user.id),
            "email": user.email,
            "username": user.username,
            "role": user.role
        })
        
        # Store session in Redis
        await self._create_session(user.id, token, user.to_dict())
        
        return user, token
    
    async def login_user(self, login_data: UserLogin) -> tuple[Optional[User], Optional[str]]:
        """Authenticate user and return JWT token."""
        # Check if login is email or phone
        user = None
        if '@' in login_data.login:
            user = await self.user_repository.get_by_email(login_data.login)
        elif login_data.login.isdigit():
            user = await self.user_repository.get_by_phone(login_data.login)
        else:
            user = await self.user_repository.get_by_username(login_data.login)
        
        if not user:
            # Check login attempts
            await self._increment_login_attempts(login_data.login)
            return None, "Invalid credentials"
        
        # Check if account is blocked due to too many attempts
        if await self._is_account_blocked(login_data.login):
            return None, "Account temporarily locked due to too many failed attempts"
        
        # Verify password
        if not verify_password(login_data.password, user.password_hash):
            await self._increment_login_attempts(login_data.login)
            return None, "Invalid credentials"
        
        # Check if user is active
        if not user.is_active:
            return None, "Account is inactive"
        
        # Clear login attempts on successful login
        await self._clear_login_attempts(login_data.login)
        
        # Update last login time
        await self.user_repository.update_last_login(user.id)
        
        # Generate JWT token
        token = create_access_token({
            "user_id": str(user.id),
            "email": user.email,
            "username": user.username,
            "role": user.role
        })
        
        # Store session in Redis
        await self._create_session(user.id, token, user.to_dict())
        
        return user, token
    
    async def refresh_token(self, user_id: str, old_token: str) -> Optional[str]:
        """Refresh JWT token."""
        # Verify session exists
        session_key = f"session:{user_id}"
        session_data = await self.redis_client.get(session_key)
        
        if not session_data:
            return None
        
        # Get user
        user = await self.user_repository.get_by_id(UUID(user_id))
        if not user or not user.is_active:
            return None
        
        # Generate new token
        new_token = create_access_token({
            "user_id": str(user.id),
            "email": user.email,
            "username": user.username,
            "role": user.role
        })
        
        # Update session
        await self._update_session(user.id, new_token, user.to_dict())
        
        return new_token
    
    async def logout_user(self, user_id: str) -> bool:
        """Logout user by removing session."""
        session_key = f"session:{user_id}"
        await self.redis_client.delete(session_key)
        return True
    
    async def request_password_reset(self, email: str) -> tuple[bool, Optional[str]]:
        """Generate password reset token."""
        user = await self.user_repository.get_by_email(email)
        if not user:
            # Don't reveal if email exists
            return True, None
        
        # Generate reset token
        reset_token = generate_password_reset_token()
        
        # Store token in Redis with expiration
        reset_key = f"reset_token:{email}"
        await self.redis_client.setex(
            reset_key,
            timedelta(minutes=settings.PASSWORD_RESET_TOKEN_EXPIRE_MINUTES),
            reset_token
        )
        
        # Return token (in production, this would be sent via email)
        return True, reset_token
    
    async def reset_password(self, email: str, token: str, new_password: str) -> tuple[bool, str]:
        """Reset user password with valid token."""
        # Verify token
        reset_key = f"reset_token:{email}"
        stored_token = await self.redis_client.get(reset_key)
        
        if not stored_token or stored_token.decode() != token:
            return False, "Invalid or expired reset token"
        
        # Check password strength
        is_strong, message = check_password_strength(new_password)
        if not is_strong:
            return False, message
        
        # Get user
        user = await self.user_repository.get_by_email(email)
        if not user:
            return False, "User not found"
        
        # Update password
        password_hash = hash_password(new_password)
        await self.user_repository.update_password(user.id, password_hash)
        
        # Delete reset token
        await self.redis_client.delete(reset_key)
        
        # Invalidate all existing sessions
        session_key = f"session:{user.id}"
        await self.redis_client.delete(session_key)
        
        return True, "Password reset successful"
    
    async def get_user_by_id(self, user_id: UUID) -> Optional[User]:
        """Get user by ID."""
        return await self.user_repository.get_by_id(user_id)
    
    async def update_user(self, user_id: UUID, user_data: UserUpdate) -> Optional[User]:
        """Update user information."""
        return await self.user_repository.update(user_id, user_data)
    
    async def _create_session(self, user_id: UUID, token: str, user_info: dict) -> None:
        """Create Redis session."""
        session_key = f"session:{user_id}"
        session_data = {
            "token": token,
            "user_info": user_info,
            "created_at": datetime.utcnow().isoformat()
        }
        
        await self.redis_client.setex(
            session_key,
            timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS),
            json.dumps(session_data)
        )
    
    async def _update_session(self, user_id: UUID, token: str, user_info: dict) -> None:
        """Update Redis session."""
        session_key = f"session:{user_id}"
        session_data = {
            "token": token,
            "user_info": user_info,
            "updated_at": datetime.utcnow().isoformat()
        }
        
        await self.redis_client.setex(
            session_key,
            timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS),
            json.dumps(session_data)
        )
    
    async def _increment_login_attempts(self, identifier: str) -> None:
        """Increment failed login attempts."""
        attempts_key = f"login_attempts:{identifier}"
        attempts = await self.redis_client.get(attempts_key)
        
        if attempts:
            attempts = int(attempts) + 1
        else:
            attempts = 1
        
        await self.redis_client.setex(
            attempts_key,
            timedelta(minutes=settings.LOGIN_ATTEMPT_WINDOW_MINUTES),
            attempts
        )
    
    async def _clear_login_attempts(self, identifier: str) -> None:
        """Clear login attempts on successful login."""
        attempts_key = f"login_attempts:{identifier}"
        await self.redis_client.delete(attempts_key)
    
    async def _is_account_blocked(self, identifier: str) -> bool:
        """Check if account is blocked due to too many failed attempts."""
        attempts_key = f"login_attempts:{identifier}"
        attempts = await self.redis_client.get(attempts_key)
        
        if attempts and int(attempts) >= settings.LOGIN_ATTEMPT_LIMIT:
            return True
        return False